﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

namespace DotNetCRM.Core.ExtensionMethods
{
    /// <summary>
    /// Registry of emited <see cref="DynamicMethod"/> delegates
    /// for improved member access via reflection.
    /// </summary>
    /// <remaks>
    /// Excluded from Code Coverage because the GetPropertyAccessor_CachedPerformance test
    /// has unpredictable results when being run for Code Coverage.
    /// </remaks>
    [CoverageExclude]
    public class ReflectionRegistry
    {
        private static Dictionary<Type, Dictionary<string, Func<object, object>>> _propertyAccessorDelegates = new Dictionary<Type, Dictionary<string, Func<object, object>>>();
        private static Dictionary<Type, Dictionary<string, Func<object, object>>> _fieldAccessorDelegates = new Dictionary<Type, Dictionary<string, Func<object, object>>>();

        /// <summary>
        /// Get property accessor
        /// </summary>
        /// <param name="objectType"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static Func<object, object> GetPropertyAccessor(Type objectType, string propertyName)
        {
            lock (_propertyAccessorDelegates)
            {
                if (_propertyAccessorDelegates.ContainsKey(objectType))
                {
                    if (_propertyAccessorDelegates[objectType].ContainsKey(propertyName))
                        return _propertyAccessorDelegates[objectType][propertyName];
                }
                else
                {
                    _propertyAccessorDelegates[objectType] = new Dictionary<string, Func<object, object>>();
                }

                PropertyInfo pi = ReflectionHelper.InternalGetProperty(objectType, propertyName, true); 
                ParameterExpression param = Expression.Parameter(typeof(object), "container");
                LambdaExpression func = Expression.Lambda(
                    // convert property return value to object
                    Expression.Convert(
                        // get property
                        Expression.Property(
                            // convert object parameter to strong type
                            Expression.Convert(param, objectType),
                            pi),
                        typeof(object)),
                    param);

                var returnValue = (Func<object, object>)func.Compile();
                _propertyAccessorDelegates[objectType][propertyName] = returnValue;
                return returnValue;
            }
        }

        /// <summary>
        /// Get field accessor
        /// </summary>
        /// <param name="objectType"></param>
        /// <param name="fieldName"></param>
        /// <returns></returns>
        public static Func<object, object> GetFieldAccessor(Type objectType, string fieldName)
        {
            lock (_fieldAccessorDelegates)
            {
                if (_fieldAccessorDelegates.ContainsKey(objectType))
                {
                    if (_fieldAccessorDelegates[objectType].ContainsKey(fieldName))
                        return _fieldAccessorDelegates[objectType][fieldName];
                }
                else
                {
                    _fieldAccessorDelegates[objectType] = new Dictionary<string, Func<object, object>>();
                }

                FieldInfo fi = ReflectionHelper.InternalGetField(objectType, fieldName, true);
                ParameterExpression param = Expression.Parameter(typeof(object), "container");
                LambdaExpression func = Expression.Lambda(
                    // convert field return value to object
                    Expression.Convert(
                        // get field
                        Expression.Field(
                            // convert object parameter to strong type
                            Expression.Convert(param, objectType),
                            fi),
                        typeof(object)),
                    param);

                var returnValue = (Func<object, object>) func.Compile();
                _fieldAccessorDelegates[objectType][fieldName] = returnValue;
                return returnValue;
            }
        }
    }
}